#ifndef __SUBSYSTEM_MOVING_BLOCKS_H__
#define __SUBSYSTEM_MOVING_BLOCKS_H__

#include <set>
#include <mutex>
#include <functional>
#include "Subsystem.hpp"
#include "Game/MovingBlock.h"
#include "Game/MovingBlocksRaycastResult.hpp"
#include <list>

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    class SubsystemTerrain;
    class SubsystemTimeOfDay;
    class PackageManager;

    class SubsystemMovingBlocks : public Subsystem
    {
    protected:
        SubsystemTerrain *m_terrain;
        SubsystemTimeOfDay *m_timeOfDay;

        std::list<MovingBlockSet *> m_movingBlockList;
        std::vector<MovingBlockSet *> m_stopped;
        std::vector<MovingBlockSet *> m_removing;
        std::mutex m_movingMutex;

        std::vector<std::function<void(MovingBlockSet *,const Point3&)>> m_collidedWithTerrainEvent;
        std::vector<std::function<void(MovingBlockSet *)>> m_stoppedEvent;
    protected:
        void movingBlocksCollision(MovingBlockSet *movingBlockSet);
        void terrainCollision(MovingBlockSet *movingBlockSet);

    public:
        static inline bool ExclusiveBoxIntersection(const BoundingBox &b1, const BoundingBox &b2);
        static void SaveMovingItemToMsgPack(MovingBlockSet* movingBlockSet, nlohmann::json &jsonData);

        inline const std::list<MovingBlockSet *> &getMovingBlockSets();
        inline void addCollidedWithTerrainEvent(std::function<void(MovingBlockSet *, const Point3&)> func);
        inline void addStoppedEvent(std::function<void(MovingBlockSet *)> func);

        MovingBlockSet *addMovingBlockSet(const Vector3 &position, const Vector3 &targetPosition, float speed, float acceleration, float drag, const Vector2 &smoothness, std::vector<MovingBlock> &blocks, std::string id, std::shared_ptr<Point3> tag, bool testCollision);
        void removeMovingBlockSet(MovingBlockSet *movingBlockSet);
        void findMovingBlocks(const BoundingBox &boundingBox, bool extendToFillCells, std::vector<MovingBlockSet *> &result);
        MovingBlockSet *findMovingBlocks(const std::string &id, std::shared_ptr<Point3> tag);
        std::shared_ptr<MovingBlocksRaycastResult> raycast(const Vector3 &start, const Vector3 &end, bool extendToFillCells);


    public:
        SubsystemMovingBlocks();

        virtual void update(float dt) override;
        virtual void load(const nlohmann::json &json) override;
        virtual void save(nlohmann::json &json) override;
        virtual void saveToMsgJson(nlohmann::json &jsonData) override;

        virtual const std::string &getName() const override;
        virtual SubUpdateType getUpdateType() override;
    };

    inline bool SubsystemMovingBlocks::ExclusiveBoxIntersection(const BoundingBox &b1, const BoundingBox &b2)
    {
        if (b1.Max.posX > b2.Min.posX && b1.Min.posX < b2.Max.posX && b1.Max.posY > b2.Min.posY && b1.Min.posY < b2.Max.posY && b1.Max.posZ > b2.Min.posZ)
        {
            return b1.Min.posZ < b2.Max.posZ;
        }
        return false;
    }

    inline void SubsystemMovingBlocks::addCollidedWithTerrainEvent(std::function<void(MovingBlockSet *, const Point3&)> func)
    {
        m_collidedWithTerrainEvent.push_back(func);
    }

    inline void SubsystemMovingBlocks::addStoppedEvent(std::function<void(MovingBlockSet *)> func)
    {
        m_stoppedEvent.push_back(func);
    }

    inline const std::list<MovingBlockSet *> &SubsystemMovingBlocks::getMovingBlockSets()
    {
        return m_movingBlockList;
    }

}

#endif
